﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Aleator.Core;
using Aleator.Core.Techniques;
using System.Threading;

namespace Aleator.Helpers
{
    /// <summary>
    /// Commonality static class
    /// </summary>
    public static class Commonality
    {
        private static NoteFactory m_notefactory = new ConcreteNoteFactory();

        private static ContinuousUniformDistribution m_continuous = new ContinuousUniformDistribution();
        private static NormalDistribution m_normal = new NormalDistribution();
        private static RayleighDistribution m_rayleigh = new RayleighDistribution();

        /// <summary>
        /// Alter an existing phrase
        /// </summary>
        /// <param name="phrase">Phrase to be altered</param>
        /// <param name="variance">Variance 1 to 5</param>
        public static void Explode(ConcretePhrase phrase)
        {
            string phrasetype = phrase.GetType().Name;

            switch (phrasetype)
            {
                case "DrumPhrase":
                    phrase.TechniqueType = phrase.Progression.Composition.Progressions[0].DrumPhrase.TechniqueType;
                    Strip((DrumPhrase)phrase);
                    Adjust((DrumPhrase)phrase);
                    //TODO: Adjust only takes care of the first measure -- need to fill out the rest. Also allow for rock/vibe transitions and make allowance for variance.
                    break;
                case "BassPhrase":
                    Strip(phrase);
                    break;
                case "RhythmPhrase":
                    Strip(phrase);
                    break;
                default:
                    throw new NotImplementedException("Only drum, bass and rhythm phrases can be disassembled!");
            }
        }

        private static void Strip(DrumPhrase dp)
        {
            DrumPhrase fromphrase = dp.Progression.Composition.Progressions[0].DrumPhrase;
            List<ConcreteNote> notes = new List<ConcreteNote>();

            dp.PhraseRhythm = fromphrase.PhraseRhythm;
            dp.Notes = new List<ConcreteNote>();

            dp.KickResolution = fromphrase.KickResolution;
            dp.SnareResolution = fromphrase.SnareResolution;
            dp.HiHatResolution = fromphrase.HiHatResolution;

            int[] kicklocations = fromphrase.KickNotes.Select(k => k.Location).Distinct().ToArray();
            int[] snarelocations = fromphrase.SnareNotes.Select(s => s.Location).Distinct().ToArray();
            int[] hihatlocations = fromphrase.HiHatNotes.Select(h => h.Location).Distinct().ToArray();

            dp.KickNotes = new ConcreteNote[kicklocations.Length];
            dp.SnareNotes = new ConcreteNote[snarelocations.Length];
            dp.HiHatNotes = new ConcreteNote[hihatlocations.Length];

            foreach (int location in kicklocations)
            {
                if (fromphrase.KickNotes.Where(k => k.Location == location).Count() == fromphrase.Progression.TotalMeasures)
                    notes.Add((ConcreteNote)m_notefactory.GetNote(location, 0, DrumPhrase.KickNum, DrumPhrase.KickOnMessageNumber, DrumPhrase.KickOffMessageNumber, dp));
            }

            notes.RemoveAll(n => n == null);
            dp.KickNotes = notes.ToArray();
            notes.Clear();

            foreach (int location in snarelocations)
                    notes.Add((ConcreteNote)m_notefactory.GetNote(location, 0, DrumPhrase.SnareNum, DrumPhrase.SnareOnMessageNumber, DrumPhrase.SnareOffMessageNumber, dp));

            notes.RemoveAll(n => n == null);
            dp.SnareNotes = notes.ToArray();
            notes.Clear();

            foreach (int location in hihatlocations)
            {
                if (fromphrase.HiHatNotes.Where(h => h.Location == location).Count() == fromphrase.Progression.TotalMeasures)
                    notes.Add((ConcreteNote)m_notefactory.GetNote(location, 0, DrumPhrase.HiHatNum, DrumPhrase.HiHatOnMessageNumber, DrumPhrase.HiHatOffMessageNumber, dp));
            }

            notes.RemoveAll(n => n == null);
            dp.HiHatNotes = notes.ToArray();
            notes.Clear();
        }

        private static void Adjust(DrumPhrase dp)
        {
            int kicks = 0;
            int snares = 0;
            int hihats = 0;

            try
            {
                kicks = dp.KickNotes.Where(k => k != null).Select(k => k.Location).Count();
                snares = dp.SnareNotes.Where(s => s != null).Select(s => s.Location).Count();
                hihats = dp.HiHatNotes.Where(h => h != null).Select(h => h.Location).Count();
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.Message);
            }

            //Bounce technique, even meter
            if (dp.TechniqueType == Technique.TechniqueType.Bounce && dp.Progression.Numerator % 2 == 0)
            {
                if (kicks != dp.Progression.Numerator / dp.KickResolution)
                    Normalize(dp.KickNotes, dp, dp.Progression.Numerator / dp.KickResolution);
                else
                    Transform(dp.KickNotes, dp, dp.Progression.Numerator / dp.KickResolution);

                if (snares != dp.Progression.Numerator / (dp.SnareResolution / 2))
                    Normalize(dp.SnareNotes, dp, dp.Progression.Numerator / (dp.SnareResolution / 2));
                else
                    Transform(dp.SnareNotes, dp, dp.Progression.Numerator / dp.SnareResolution);

                int normalhh = dp.Progression.Numerator * (dp.HiHatResolution / 2);

                if (hihats < normalhh - (normalhh / 2) || hihats > normalhh + (normalhh / 2))
                    Normalize(dp.HiHatNotes, dp, dp.Progression.Numerator * (dp.HiHatResolution / 2));
                else
                    Transform(dp.HiHatNotes, dp, dp.Progression.Numerator / dp.HiHatResolution);
            }
            else if (dp.TechniqueType == Technique.TechniqueType.Vibe)
                Shift(dp);
            else if (dp.TechniqueType == Technique.TechniqueType.Rock)
                Shift(dp);

            Fill(dp);
        }

        private static void Strip(ConcretePhrase p)
        {
            ConcretePhrase fromphrase;

            if (p.GetType() == typeof(BassPhrase))
                fromphrase = p.Progression.Composition.Progressions[0].BassPhrase;
            else if (p.GetType() == typeof(RhythmPhrase))
                fromphrase = p.Progression.Composition.Progressions[0].RhythmPhrase;
            else
                throw new NotImplementedException("Default disassembly not implemented for this phrase type.");

            int ctr = 0;

            int[] locations = fromphrase.Notes.Select(n => n.Location).Distinct().ToArray();

            Parallel.ForEach(locations, location =>
            {
                if (fromphrase.Notes.Where(h => h.Location == location).Count() == fromphrase.Progression.TotalMeasures)
                    p.Notes[ctr++] = (ConcreteNote)m_notefactory.GetNote(location, 0, DrumPhrase.HiHatNum, DrumPhrase.HiHatOnMessageNumber, DrumPhrase.HiHatOffMessageNumber, p);
            });
        }

        //Create a slight reggae variation of the drum phrase passed in
        private static void Shift(DrumPhrase dp)
        {
            int kickcount = dp.KickNotes.Count();
            int snarecount = dp.SnareNotes.Count();
            int hihatcount = dp.HiHatNotes.Count();

            int target;
            int selector;

            //These calculations arent being done properly. Should be using normal distributiion object, not a random generator...
            switch (dp.PhraseRhythm.RhythmType)
            {
                case 1:
                    selector = (int)(dp.Progression.Numerator * .75);
                    break;
                case 2:
                    throw new NotImplementedException("No shift implementation for freestyle technique");
                case 3:
                    selector = (int)(dp.Progression.Numerator * .5);
                    break;
                default:
                    selector = 3;
                    break;
            }

            int ctr = dp.KickNotes.Length;

            //Alter kick
            if (selector > kickcount)
            {
                ConcreteNote[] notes = dp.KickNotes.ToArray();
                Array.Resize(ref notes, notes.Length + (selector - notes.Length));

                while (notes.Where(k => k == null).Any())
                {
                    Vibe.Instance.GetBeat(dp.Progression.Numerator, dp.Resolution, ctr);

                    if (!notes.Where(k => k != null && k.Location == Vibe.Instance.Location).Any())
                        notes[ctr++] = (ConcreteNote)m_notefactory.GetNote(Vibe.Instance.Location, 0, DrumPhrase.KickNum, DrumPhrase.KickOnMessageNumber, DrumPhrase.KickOffMessageNumber, dp);
                }

                dp.KickNotes = notes;
            }
            else if (selector < kickcount)
            {
                ConcreteNote[] notes = dp.KickNotes.OrderBy(x => Session.RndGenerator.Next()).ToArray();
                Array.Resize(ref notes, notes.Length - (notes.Length - selector));

                dp.KickNotes = notes;
            }

            if (dp.PhraseRhythm.RhythmType == 3)
                target = 1;
            else
                target = 2;

            //Alter snare
            selector = snarecount;

            if (selector < target)
            {
                ConcreteNote[] notes = dp.SnareNotes.ToArray();
                Array.Resize(ref notes, notes.Length + 1);

                while (notes.Where(n => n == null).Any())
                {
                    int location = Session.RndGenerator.Next(dp.Progression.Numerator * dp.SnareResolution);

                    if (!notes.Where(s => s != null && s.Location == location).Any())
                        notes[selector] = (ConcreteNote)m_notefactory.GetNote(Vibe.Instance.Location, 0, DrumPhrase.SnareNum, DrumPhrase.SnareOnMessageNumber, DrumPhrase.SnareOffMessageNumber, dp);
                }

                dp.SnareNotes = notes;
            }
            else
            {
                if (snarecount > 1 || Session.RndGenerator.Next(1) == 0)
                {
                    ConcreteNote[] notes = dp.SnareNotes.OrderBy(x => Session.RndGenerator.Next()).ToArray();
                    Array.Resize(ref notes, notes.Length - 1);

                    dp.SnareNotes = notes;
                }
            }

            //Alter HiHat
            selector = hihatcount;
            ctr = selector;

            if (selector < dp.Progression.Numerator * dp.HiHatResolution)
            {
                int adds = Session.RndGenerator.Next((dp.Progression.Numerator * dp.HiHatResolution) - selector);

                ConcreteNote[] notes = dp.HiHatNotes.ToArray();
                Array.Resize(ref notes, notes.Length + adds);

                while (notes.Where(n => n == null).Any())
                {
                    int location = Session.RndGenerator.Next(dp.Progression.Numerator * dp.HiHatResolution);

                    if (!notes.Where(h => h != null && h.Location == location).Any())
                        notes[ctr++] = (ConcreteNote)m_notefactory.GetNote(Vibe.Instance.Location, 0, DrumPhrase.HiHatNum, DrumPhrase.HiHatOnMessageNumber, DrumPhrase.HiHatOffMessageNumber, dp);
                }

                dp.HiHatNotes = notes;
            }
            else if (selector > dp.Progression.Numerator * dp.HiHatResolution)
            {
                //Remove some
                int deletes = Session.RndGenerator.Next(selector - (dp.Progression.Numerator * dp.HiHatResolution));

                ConcreteNote[] notes = dp.HiHatNotes.OrderBy(x => Session.RndGenerator.Next()).ToArray();
                Array.Resize(ref notes, notes.Length - deletes);

                dp.HiHatNotes = notes;
            }
        }

        //Drum phrases
        private static void Normalize(ConcreteNote[] notes, DrumPhrase dp, int target)
        {
            int notenumber = notes[0].NoteID;
            int noteonmsg = notes[0].MessageOnID;
            int noteoffmsg = notes[0].MessageOffID;

            int resolution;

            if (notenumber == DrumPhrase.KickNum)
                resolution = dp.KickResolution;
            else if (notenumber == DrumPhrase.SnareNum)
                resolution = dp.SnareResolution;
            else
                resolution = dp.HiHatResolution;

            ConcreteNote[] newnotes;

            int normalize_int;
            int starttotal = notes.Length;
            int ctr = notes.Length;

            if (target > starttotal)
            {
                newnotes = notes.ToArray();
                normalize_int = Session.RndGenerator.Next(target - starttotal);

                Array.Resize(ref newnotes, newnotes.Length + normalize_int);

                while (normalize_int > 0)
                {
                    int location = Session.RndGenerator.Next(dp.Progression.Numerator * resolution);

                    if (!newnotes.Where(h => h.Location == location).Any())
                    {
                        newnotes[ctr++] = (ConcreteNote)m_notefactory.GetNote(Vibe.Instance.Location, 0, notenumber, noteonmsg, noteoffmsg, dp);
                        normalize_int--;
                    }
                }

                notes = newnotes;
            }
            else if (target < starttotal)
            {
                newnotes = notes.OrderBy(x => Session.RndGenerator.Next()).ToArray();
                normalize_int = Session.RndGenerator.Next(starttotal - target);

                Array.Resize(ref newnotes, newnotes.Length - normalize_int);

                notes = newnotes;
            }
        }

        //All other phrases
        private static void Normalize(ConcretePhrase cp, int target)
        {
            //TODO: Complete and uncomment

            //Note.NoteType nt = cp.Notes[0].Type;

            //int normalize_int;
            //int starttotal = cp.Notes.Count;

            //int notenumber = 0;
            //int noteonmsg = 0;
            //int noteoffmsg = 0;

            //List<ConcreteNote> newnotes = cp.Notes.ToList();

            ////Add or remove notes to guide note total toward mu
            //if (target > starttotal)
            //{
            //    normalize_int = Session.RndGenerator.Next(target - starttotal);

            //    while (normalize_int > 0 )
            //    {
            //        int location = Session.RndGenerator.Next(cp.Progression.Numerator * cp.Resolution);

            //        if (!newnotes.Where(h => h.Location == location).Any())
            //        {
            //            newnotes.Add((ConcreteNote)m_notefactory.GetNote(Vibe.Instance.Location, 0, notenumber, noteonmsg, noteoffmsg, cp));
            //            normalize_int--;
            //        }
            //    }

            //    cp.Notes.Clear();
            //    cp.Notes.AddRange(newnotes);
            //}
            //else if (target < starttotal)
            //{
            //    normalize_int = Session.RndGenerator.Next(starttotal - target);

            //    while (normalize_int > 0)
            //    {
            //        newnotes.RemoveAt(Session.RndGenerator.Next(newnotes.Count()));
            //        normalize_int--;
            //    }
            //}
        }

        //TODO: Implement for drum phrases
        private static void Transform(ConcreteNote[] notes, DrumPhrase dp, int target)
        {
            //Add or remove notes to guide note total away from mu
            int notenumber = notes[0].NoteID;
            int noteonmsg = notes[0].MessageOnID;
            int noteoffmsg = notes[0].MessageOffID;

            int resolution;

            if (notenumber == DrumPhrase.KickNum)
                resolution = dp.KickResolution;
            else if (notenumber == DrumPhrase.SnareNum)
                resolution = dp.SnareResolution;
            else
                resolution = dp.HiHatResolution;

            ConcreteNote[] newnotes;

            int normalize_int;
            int starttotal = notes.Length;
            int ctr = notes.Length;

            //Force away from the median
            if (target > starttotal)
            {
                newnotes = notes.OrderBy(x => Session.RndGenerator.Next()).ToArray();
                normalize_int = Session.RndGenerator.Next(starttotal - target);

                Array.Resize(ref newnotes, newnotes.Length - normalize_int);

                notes = newnotes;
            }
            else if (starttotal < target)
            {
                newnotes = notes.ToArray();
                normalize_int = Session.RndGenerator.Next(target - starttotal);

                Array.Resize(ref newnotes, newnotes.Length + normalize_int);

                while (normalize_int > 0)
                {
                    int location = Session.RndGenerator.Next(dp.Progression.Numerator * resolution);

                    if (!newnotes.Where(h => h.Location == location).Any())
                    {
                        newnotes[ctr++] = (ConcreteNote)m_notefactory.GetNote(Vibe.Instance.Location, 0, notenumber, noteonmsg, noteoffmsg, dp);
                        normalize_int--;
                    }
                }

                notes = newnotes;
            }
        }

        //All other phrases
        private static void Transform(ConcretePhrase cp)
        {

        }

        private static void Fill(DrumPhrase dp)
        {
            ConcreteNote tmpNote;
            ConcreteNote[] tmpNotes;

            //Determine how many kicks are in each measure without the remainder
            int kicksInMeasure = dp.KickNotes.Length;
            int snaresInMeasure = dp.SnareNotes.Length;
            int hihatsInMeasure = dp.HiHatNotes.Length;

            //Fill out the rest
            int j = kicksInMeasure;
            tmpNotes = dp.KickNotes.ToArray();

            Array.Resize(ref tmpNotes, dp.KickNotes.Length * dp.Progression.TotalMeasures);

            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * kicksInMeasure)
                    break;

                for (int k = 0; k < kicksInMeasure; k++)
                {
                    tmpNote = dp.KickNotes[j % kicksInMeasure];
                    tmpNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.KickNum, DrumPhrase.KickOnMessageNumber, DrumPhrase.KickOffMessageNumber, dp);
                    //dp.Notes.Add(dp.KickNotes[j]);
                    j++;
                }
            }

            dp.KickNotes = tmpNotes.ToArray();
            dp.Notes.AddRange(dp.KickNotes);

            j = snaresInMeasure;
            tmpNotes = dp.SnareNotes.ToArray();

            Array.Resize(ref tmpNotes, dp.SnareNotes.Length * dp.Progression.TotalMeasures);

            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * snaresInMeasure)
                    break;

                for (int k = 0; k < snaresInMeasure; k++)
                {
                    tmpNote = dp.SnareNotes[j % snaresInMeasure];
                    tmpNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.SnareNum, DrumPhrase.SnareOnMessageNumber, DrumPhrase.SnareOffMessageNumber, dp);
                    //dp.Notes.Add(dp.SnareNotes[j]);
                    j++;
                }
            }

            dp.SnareNotes = tmpNotes.ToArray();
            dp.Notes.AddRange(dp.SnareNotes);

            j = hihatsInMeasure;
            tmpNotes = dp.HiHatNotes.ToArray();

            Array.Resize(ref tmpNotes, dp.HiHatNotes.Length * dp.Progression.TotalMeasures);

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * hihatsInMeasure)
                    break;

                for (int k = 0; k < hihatsInMeasure; k++)
                {
                    tmpNote = dp.HiHatNotes[j % hihatsInMeasure];
                    tmpNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.HiHatNum, DrumPhrase.HiHatOnMessageNumber, DrumPhrase.HiHatOffMessageNumber, dp);
                    //dp.Notes.Add(dp.HiHatNotes[j]);
                    j++;
                }
            }

            dp.HiHatNotes = tmpNotes.ToArray();
            dp.Notes.AddRange(dp.HiHatNotes);
        }
    }
}
